

from asyncore import dispatcher
from asynchat import async_chat
import asyncore, socket

PORT = 5005
NAME = 'ChatRoom'


class CommandHelper:
    def unknown(self, session, cmd):
        session.push(('Unknown command: %s\r\n' % cmd).encode('utf-8', 'ignore'))
        
    def handle(self, session, line):
        if not line.strip():
            return
        parts = line.split(' ', 1)
        cmd = parts[0]
        try: line = parts[1].strip()
        except IndexError: line = ''
        meth = getattr(self, 'do_' + cmd, None)
        try:
            meth(session, line)
        except TypeError:
            self.unknown(session, cmd)
            
class EndSession(Exception):
    pass

class Room(CommandHelper):
    def __init__(self, server):
        self.server = server
        self.sessions = []
        
    def add(self, session):
        self.sessions.append(session)
        
    def remove(self, session):
        self.sessions.remove(session)
        
    def broadcast(self, line):
        for session in self.sessions:
            session.push((line + '\r\n').encode('utf-8', 'ignore'))
            
    def do_logout(self, session, line):
        raise EndSession
    
class LoginRoom(Room):
    def add(self, session):
        self.broadcast(('Welcome to %s\r\n' % self.server.name))
        
    def unknown(self, session, cmd):
        session.push(('Please login\nUse "login <nick>"\r\n').encode('utf-8', 'ignore'))
        
    def do_login(self, session, line):
        name = line.strip()
        if not name:
            session.push(('please enter a name\r\n').encode('utf-8', 'ignore'))
        elif name in self.server.users:
            session.push(('The name %s is taken\r\n' % name).encode('utf-8', 'ignore'))
            session.push(('Please try again\r\n').encode('utf-8', 'ignore'))
        else:
            session.name = name
            session.enter(self.server.main_room)
            
class ChatRoom(Room):
    def add(self, session):
        self.broadcast(session.name + ' has entered the room\r\n')
        self.server.users[session.name] = session
        Room.add(self, session)
        
    def remove(self, session):
        Room.remove(self, session)
        self.broadcast(session.name + ' has left the room\r\n')
        
    def do_say(self, session, line):
        self.broadcast(session.name + ': ' + line + '\r\n')
        
    def do_look(self, session, line):
        session.push(('The following are in this room: \r\n').encode('utf-8', 'ignore'))
        for other in self.sessions:
            session.push((other.name + '\r\n').encode('utf-8', 'ignore'))
            
    def do_who(self, session, line):
        session.push(('The following are logged in:\r\n').encode('utf-8', 'ignore'))
        for name in self.server.users:
            session.push((name + '\r\n').encode('utf-8', 'ignore'))
            
class LogoutRoom(Room):
    def add(self, session):
        try: del self.server.users[session.name]
        except KeyError: pass
        

class ChatSession(async_chat):
    
    def __init__(self, server, sock):
        async_chat.__init__(self, sock)
        self.server = server
        self.set_terminator(b'\r\n')
        self.data = []
        self.name = None
        self.enter(LoginRoom(server))
        
    def enter(self, room):
        try: cur = self.room
        except AttributeError: pass
        else:
            try: cur.remove(self)
            except ValueError: pass
        self.room = room
        room.add(self)
        
    def collect_incoming_data(self, data):
        self.data.append(data.decode('utf-8', 'ignore'))
        
    def found_terminator(self):
        line = ''.join(self.data)
        self.data = []
        try: self.room.handle(self, line)
        except EndSession: self.handle_close()
        
    def handle_close(self):
        async_chat.handle_close(self)
        self.enter(LogoutRoom(self.server))
        


class ChatServer(dispatcher):
    def __init__(self, port, name):
        dispatcher.__init__(self)
        self.name = name
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(('', port))
        self.listen(5)
        self.users = {}
        self.main_room = ChatRoom(self)
        
    def handle_accept(self):
        conn, addr = self.accept()
        print('connection attempt from ', addr[0])
        ChatSession(self, conn)


if __name__ == '__main__':
    s = ChatServer(PORT, NAME)
    try: asyncore.loop()
    except KeyboardInterrupt: print